/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.features.signature_factory;

import picounit.features.base.ProxyFactory;
import picounit.features.registry.Resolver;
import picounit.mocker.MockFactory;

import java.lang.reflect.InvocationHandler;

public class Parameter<T> {
	public static final Converter<Parameter, Object> VALUE_CONVERTER =
		new AbstractConverter<Parameter, Object>(Object.class) {
			public Object convert(Parameter parameter) {
				Object value = parameter.getValue();
				
//				System.out.println(String.format("Parameter: %s, class: %s, classloader: %s", value,
//					value.getClass(), value.getClass().getClassLoader()));
				
				return value;
			}
	};

	private final String name;
	private final Class<T> type;
	private final Class declaringClass;

	private final MockFactory mockFactory;
	private final Resolver ordinaryResolver;
	private final ProxyFactory proxyFactory;
	private final InvocationHandler collaboratorsInvocationHandler;
	private final Signature signature;

	@SuppressWarnings("unchecked")
	public static Parameter[] create(Signature signature, String[] parameterNames, Class[] parameterTypes,
		Class declaringClass, MockFactory mockFactory, Resolver ordinaryResolver, ProxyFactory proxyFactory,
		InvocationHandler collaboratorsInvocationHandler) {

		Parameter[] parameters = new Parameter[parameterTypes.length];

		for (int index = 0; index < parameters.length; index++ ) {
			parameters[index] = new Parameter(signature, declaringClass, parameterNames[index],
				parameterTypes[index], mockFactory, ordinaryResolver, proxyFactory, collaboratorsInvocationHandler);
		}

		return parameters;
	}

	public Parameter(Signature signature, Class declaringClass, String name, Class<T> type, MockFactory mockFactory,
		Resolver ordinaryResolver, ProxyFactory proxyFactory,
		InvocationHandler collaboratorsInvocationHandler) {

		this.signature = signature;
		this.name = name;
		this.type = type;
		this.declaringClass = declaringClass;
		this.mockFactory = mockFactory;
		this.ordinaryResolver = ordinaryResolver;
		this.proxyFactory = proxyFactory;
		this.collaboratorsInvocationHandler = collaboratorsInvocationHandler;
	}

	public String name() {
		return name;
	}

	public Class<T> type() {
		return type;
	}

	public Object getValue() {
		if (shouldMock()) {
			return mock(); 
		}
		else if (shouldStub()) {
			return mockFactory.stub(type, ordinaryResolver.get(name, type));
		}
		else if (shouldUseCollaboratorContainer()) {
			return proxyFactory.create(type, collaboratorsInvocationHandler); 
		}
		else {
			return ordinaryResolver.get(name, type);
		}
	}

	public int numMatching() {
		if (shouldMock() || shouldStub() || shouldUseCollaboratorContainer()) {
			return 1;
		}

		return ordinaryResolver.numMatching(name, type());
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Implementation methods
	///////////////////////////////////////////////////////////////////////////////////////////////

	private boolean shouldMock() {
		if (signature.name().equals("mock")) {
			return true;
		}

		String mockPrefix = "mock";

		if (nameWithOuter().startsWith("Mock")) {
			mockPrefix += "Mock";
		}

		return startsWith(mockPrefix);
	}

	private boolean shouldStub() {
		return startsWith("stub");
	}

	private boolean shouldUseCollaboratorContainer() {
		return declaringClass.equals(type.getDeclaringClass());
	}

	private boolean startsWith(String startsWith) {
		return name != null && name.startsWith(startsWith) && name.length() > 4 &&
			Character.isUpperCase(name.charAt(4));
	}

	private String nameWithOuter() {
		String fullName = type.getName();

		return fullName.substring(fullName.lastIndexOf("$") + 1);
	}

	private T mock() {
		try {
			return name != null ? mockFactory.mock(type, name) : mockFactory.mock(type);
		}
		catch (RuntimeException runtimeException) {
			System.err.println("mocking type: " + type.getName() + ", in method: " +
				declaringClass.getName() + "." + signature.name());

			runtimeException.printStackTrace();

			throw runtimeException;
		}
	}
}